package com.wjr.braveheart.utils;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Looper;
import android.os.Process;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;

import com.wjr.braveheart.R;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Rayhahah
 * @blog http://www.jianshu.com/u/ec42ce134e8d
 * @Github https://github.com/Rayhahah
 * @time 2017/6/4
 * @fuction 捕获应用Crash信息，同时上传到服务器和写入本地文件保存
 * @usage App中getInstance().init(context, crashUploader)
 * 注意：一旦异常出现在Activity的super.onCreate()方法之前，该Activity会重启，再次抛出异常，循环往复
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {

    //Log Tag
    public static final String TAG = "CrashHandler";
    /**
     * 错误报告文件的扩展名
     */
    private static final String CRASH_REPORTER_EXTENSION = ".log";
    //异常信息
    public static final String EXCEPTION_INFO_STRING = "EXCEPTION_INFO_STRING";
    //应用信息
    public static final String PACKAGE_INFO_MAP = "PACKAGE_INFO_MAP";
    //设备数据信息
    public static final String BUILD_INFO_MAP = "BUILD_INFO_MAP";
    //系统常规配置信息
    public static final String SYSTEM_INFO_MAP = "SYSTEM_INFO_MAP";
    //手机安全配置信息
    public static final String SECURE_INFO_MAP = "SECURE_INFO_MAP";
    //内存情况信息
    public static final String MEMORY_INFO_STRING = "MEMORY_INFO_STRING";

    public static final String VERSION_NAME = "versionName";
    public static final String VERSION_CODE = "versionCode";

    //系统默认的UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    //CrashHandler实例
    private static volatile CrashHandler sInstance;
    //程序的Context对象
    private Context mContext;
    //用来存储设备信息和异常信息
    private ConcurrentHashMap<String, Object> mInfo = new ConcurrentHashMap<>();
    //CrashLog路径
    private String mDirPath;
    //异常监听
    private CrashListener mCrashListener;

    private CrashHandler() {

    }

    public static CrashHandler getInstance() {
        if (sInstance == null) {
            synchronized (CrashHandler.class) {
                if (sInstance == null) {
                    sInstance = new CrashHandler();
                }
            }
        }
        return sInstance;
    }

    /**
     * 初始化
     *
     * @param context       上下文
     * @param crashUploader 崩溃信息上传接口回调
     */
    public void init(Context context, String dirPath, CrashListener crashUploader) {
        mContext = context;
        mDirPath = TextUtils.isEmpty(dirPath) ?
                context.getExternalFilesDir("crash").getAbsolutePath() :
                context.getExternalFilesDir(dirPath).getAbsolutePath();
        File mDirectory = new File(mDirPath);
        if (!mDirectory.exists()) {
            mDirectory.mkdirs();
        }
        mCrashListener = crashUploader;
        //保存一份系统默认的CrashHandler
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //使用我们自定义的异常处理器替换程序默认的
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 这个是最关键的函数，当程序中有未被捕获的异常，系统将会自动调用uncaughtException方法
     *
     * @param t 出现未捕获异常的线程
     * @param e 未捕获的异常，有了这个ex，我们就可以得到异常信息
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        if (!catchCrashException(e) && mDefaultHandler != null) {
            //没有自定义的CrashHandler的时候就调用系统默认的异常处理方式
            mDefaultHandler.uncaughtException(t, e);
        } else {
            //退出应用
            killProcess();
        }
    }


    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @param ex
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private boolean catchCrashException(Throwable ex) {
        if (ex == null) {
            return false;
        }

        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                mCrashListener.handle();
                Looper.loop();
            }
        }.start();

        //收集设备参数信息
        collectInfo(mContext, ex);
        //保存日志文件
        saveCrashInfo2File(ex);
        //上传崩溃信息
        uploadCrashMessage(mInfo);

        return true;
    }

    /**
     * 退出应用
     */
    private static void killProcess() {
        //结束应用
        try {
            Thread.sleep(2000);
        } catch (InterruptedException ex) {
            Log.e(TAG, "CrashHandler.InterruptedException--->" + ex.toString());
        }
        //退出程序
        Process.killProcess(Process.myPid());
        System.exit(1);
    }

    /**
     * 获取设备参数信息
     *
     * @param context
     */
    private void collectInfo(Context context, Throwable ex) {
        //将Info存储起来
        mInfo.put(EXCEPTION_INFO_STRING, collectExceptionInfo(ex));
        mInfo.put(PACKAGE_INFO_MAP, collectPackageInfo(context));
        mInfo.put(BUILD_INFO_MAP, collectBuildInfo());
        mInfo.put(SYSTEM_INFO_MAP, collectSystemInfo());
        mInfo.put(SECURE_INFO_MAP, collectSecureInfo());
        mInfo.put(MEMORY_INFO_STRING, collectMemInfo());
    }

    /**
     * 将崩溃日志信息写入本地文件
     *
     * @param ex
     */
    private void saveCrashInfo2File(Throwable ex) {
        String time = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
        File file = new File(mDirPath + File.separator + "CrashLog-" + time + CRASH_REPORTER_EXTENSION);
        try {
            PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(file)));
            printWriter.println(time);
            dumpPhoneInfo(printWriter);
            printWriter.println();
            ex.printStackTrace(printWriter);
            printWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 上传崩溃信息到服务器
     */
    private void uploadCrashMessage(ConcurrentHashMap<String, Object> info) {
        mCrashListener.uploadCrashMessage(info);
    }

    /**
     * 获取捕获异常的信息
     *
     * @param ex 异常
     * @return 异常的信息
     */
    private String collectExceptionInfo(Throwable ex) {
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        ex.printStackTrace();
        Throwable throwable = ex.getCause();
        // 迭代栈队列把所有的异常信息写入writer中
        while (throwable != null) {
            throwable.printStackTrace(printWriter);
            // 换行 每个个异常栈之间换行
            printWriter.append("\r\n");
            throwable = throwable.getCause();
        }
        // 记得关闭
        printWriter.close();
        return writer.toString();
    }

    /**
     * 获取内存信息
     * 调试结果：暂无信息--需要修改
     *
     * @return 内存信息
     */
    private String collectMemInfo() {
        BufferedReader br = null;
        StringBuffer sb = new StringBuffer();

        ArrayList<String> commandLine = new ArrayList<>();
        commandLine.add("dumpsys");
        commandLine.add("memInfo");
        commandLine.add(Integer.toString(Process.myPid()));
        try {
            java.lang.Process process = Runtime.getRuntime()
                    .exec(commandLine.toArray(new String[commandLine.size()]));
            br = new BufferedReader(new InputStreamReader(process.getInputStream()), 8192);

            while (true) {
                String line = br.readLine();
                if (line == null) {
                    break;
                }
                sb.append(line);
                sb.append("\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }

    /**
     * 获取系统安全设置信息
     * 调试结果：暂无信息--需要修改
     *
     * @return 系统安全设置信息
     */
    private ConcurrentHashMap<String, String> collectSecureInfo() {
        ConcurrentHashMap<String, String> secureInfoHashMap = new ConcurrentHashMap<>();
        Field[] fields = Settings.Secure.class.getFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Deprecated.class)
                    && field.getType() == String.class
                    && field.getName().startsWith("WIFI_AP")) {
                try {
                    String value = Settings.Secure.getString(mContext.getContentResolver(), (String) field.get(null));
                    if (value != null) {
                        secureInfoHashMap.put(field.getName(), value);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return secureInfoHashMap;
    }

    /**
     * 获取系统常规设定属性
     *
     * @return 系统常规设定属性
     */
    private ConcurrentHashMap<String, String> collectSystemInfo() {
        ConcurrentHashMap<String, String> systemInfoHashMap = new ConcurrentHashMap<>();
        Field[] fields = Settings.System.class.getFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Deprecated.class)
                    && field.getType() == String.class) {
                try {
                    String value = Settings.System.getString(mContext.getContentResolver(), (String) field.get(null));
                    if (value != null) {
                        systemInfoHashMap.put(field.getName(), value);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return systemInfoHashMap;
    }

    /**
     * 从系统属性中提取设备硬件和版本信息
     *
     * @return 设备硬件和版本信息
     */
    private ConcurrentHashMap<String, String> collectBuildInfo() {
        ConcurrentHashMap<String, String> deviceInfoHashMap = new ConcurrentHashMap<>();
        // 反射机制
        Field[] fields = Build.class.getDeclaredFields();
        // 迭代Build的字段key-value 此处的信息主要是为了在服务器端手机各种版本手机报错的原因
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                deviceInfoHashMap.put(field.getName(), field.get("").toString());
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return deviceInfoHashMap;
    }

    /**
     * 获取应用参数信息
     *
     * @param context
     * @return 应用参数信息
     */
    private ConcurrentHashMap<String, String> collectPackageInfo(Context context) {
        ConcurrentHashMap<String, String> packageInfoHashMap = new ConcurrentHashMap<>();
        try {
            // 获得包管理器
            PackageManager packageManager = context.getPackageManager();
            // 得到该应用的信息，即主Activity
            PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (packageInfo != null) {
                String versionName = packageInfo.versionName == null ? "null" : packageInfo.versionName;
                String versionCode = packageInfo.versionCode + "";
                packageInfoHashMap.put(VERSION_NAME, versionName);
                packageInfoHashMap.put(VERSION_CODE, versionCode);
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return packageInfoHashMap;
    }

    /**
     * 将HashMap遍历转换成StringBuffer
     *
     * @param info
     * @return StringBuffer
     */
    @NonNull
    private static StringBuffer getInfoStr(ConcurrentHashMap<String, String> info) {
        StringBuffer mStringBuffer = new StringBuffer();
        for (Map.Entry<String, String> entry : info.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            mStringBuffer.append(key + "=" + value + "\r\n");
        }
        return mStringBuffer;
    }

    /**
     * 获取错误报告文件名
     * getFilesDir()获取该文件夹文件进行匹配筛选
     * 有待验证
     *
     * @param context
     * @return
     */
    public String[] getCrashReportFiles(Context context) {
        File filesDir = context.getFilesDir();
        FilenameFilter filter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(CRASH_REPORTER_EXTENSION);
            }
        };
        return filesDir.list(filter);
    }

    /**
     * 写入App信息
     *
     * @param printWriter
     */
    private void dumpPhoneInfo(PrintWriter printWriter) {
        try {
            PackageManager packageManager = mContext.getPackageManager();
            PackageInfo packageInfo = packageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);

            //APP名称
            printWriter.print("APP Name:");
            printWriter.println(mContext.getString(R.string.app_name));

            //APP版本
            printWriter.print("APP Version:");
            printWriter.println(packageInfo.versionName);

            //APP版本号
            printWriter.print("APP Version Code:");
            printWriter.println(packageInfo.versionCode);

            //Android 版本
            printWriter.print("Android Version:");
            printWriter.println(Build.VERSION.RELEASE);
            //Android SDK
            printWriter.print("Android SDK:");
            printWriter.println(Build.VERSION.SDK_INT);

            //手机制造商
            printWriter.print("Vendor:");
            printWriter.println(Build.MANUFACTURER);

            //手机型号
            printWriter.print("Model:");
            printWriter.println(Build.MODEL);

            //CPU架构
            printWriter.print("CPU ABI:");
            printWriter.println(Build.CPU_ABI);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 崩溃信息上传接口回调
     */
    public interface CrashListener {
        void handle();

        void uploadCrashMessage(ConcurrentHashMap<String, Object> info);
    }


}
